﻿/*
 * Cratos Tools - Data Object
 * Cratos Data Object Collection class
 * 
 * Copyright (c) 2011-2012 Draco
 * 
 * This file is part of Cratos.
 * 
 * Cratos is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * Cratos is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Cratos.  If not, see <http://www.gnu.org/licenses/>.
 * 
 *       .==.        .==.
 *      //'^\\      //^'\\
 *     // ^ ^\(\__/)/^ ^^\\
 *    //^ ^^ ^/6  6\ ^^ ^ \\
 *   //^ ^^ ^/( .. )\^ ^ ^ \\
 *  // ^^ ^/\| V""V|/\^ ^ ^ \\
 * //^^/\/ /  '~~'  \ \/\^ ^ \\
 *  ------------------------------------
 *  HERE BE DRAGONS
 *  
 *  Johan Dercksen - johan@draco.co.za
 */
using System;
using System.IO;
using System.Linq;
using System.Text;
using System.Windows;
using System.ComponentModel;
using System.Runtime.Remoting;
using System.Xml.Serialization;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Runtime.Serialization.Formatters.Binary;

using Microsoft.Practices.Unity;

using Cratos.Tools.General;

namespace Cratos.Tools.DataObject
{
	/// <summary>
	/// Cratos Data Object Collection class
	/// </summary>
	[Serializable]
	public class DOCratosCollection<T> : BindingList<T>, IDisposable, IDOCratosCollection<T>, INotifyPropertyChanged where T : IDOCratos, new()
	{
		#region Class Constant declarations
		#endregion

		#region Class Event declarations
		/// <summary>
		/// Property Changed event
		/// </summary>
		[field: NonSerialized]
		public event PropertyChangedEventHandler PropertyChanged;
		#endregion

		#region Class Variable declarations
		private bool																										isDisposed;
		private DOCratosCollectionAction																collectionAction = DOCratosCollectionAction.None;

		private Collection<CratosError>																	doErrors = new Collection<CratosError>();
		private Collection<DOCratosActionAttribute>											doActions;
		private Dictionary<DOCratosCollectionAction, List<CratosRule>>	currentRules;
		#endregion

		#region Class Constructors & Destructors
		/// <summary>
		/// Class Constructor
		/// </summary>
		public DOCratosCollection() : base()
		{
			// Set the relevant default properties
			this.DOGuid                 = Guid.NewGuid();
			this.CollectionActionResult = DOCratosActionResult.None;
			this.CollectionActionName   = "";
			this.CollectionAction       = DOCratosCollectionAction.None;
			this.IsCacheRetrieve        = false;
			this.MaxRecords						  = DOCratosGlobal.CollectionMaxNoRecords;

			// Create and Setup the relevant object(s)
			this.DataObject                  = new T();
			this.DataObject.DBConnectionName = DOCratosGlobal.DBConnectionName;
			this.DataObject.DBActionType     = DOCratosActionType.StoredProcedure;

			// Clear and Initialize the relevant Rules the object
			this.Clear();
			this.InitializeRules();
		}

		/// <summary>
		/// Performs application-defined tasks associated with freeing, releasing, or
		/// resetting unmanaged resources.
		/// </summary>
		public void Dispose() { Dispose(true); GC.SuppressFinalize(this); }

		/// <summary>
		/// Dispose method doing the disposing
		/// </summary>
		/// <param name="isDisposing"></param>
		protected virtual void Dispose(bool isDisposing)
		{
			// Declare the required variables

			// Check to see if Dispose has already been called.
			if (!this.isDisposed)
			{
				// Dispose the objects if required
				if (isDisposing)
				{
				}
			}

			// Set the disposed indicator
			this.isDisposed = true;

			// Return to calling method
			return;
		}
		#endregion

		#region Class Properties
		/// <summary>
		/// DO Collection GUID
		/// </summary>
		public Guid DOGuid { get; set; }

		/// <summary>
		/// Ignore Unity Indicator
		/// </summary>
		public bool IgnoreUnity { get; set; }

		/// <summary>
		/// Data Object List Action Result
		/// </summary>
		public DOCratosActionResult CollectionActionResult { get; set; }

		/// <summary>
		/// Data Object Action Name
		/// </summary>
		public string CollectionActionName { get; set; }

		/// <summary>
		/// Collection of Errors
		/// </summary>
		public Collection<CratosError> Errors
		{
			get { return (this.doErrors); }
		}

		/// <summary>
		/// Cache Retrieve indicator
		/// </summary>
		public bool IsCacheRetrieve { get; set; }

		/// <summary>
		/// Cratos Data Object Actions
		/// </summary>
		[XmlIgnore]
		public Collection<DOCratosActionAttribute> AllDOActions
		{
			get
			{
				// Declare the required variables

				// Make sure we have an object to work with
				if (this.doActions == null)
				{
					// Create the relevant object
					this.doActions = new Collection<DOCratosActionAttribute>();

					// Retrieve all the Cratos Actions associated with the class
					DOCratosActionAttribute[] allActions = (DOCratosActionAttribute[])this.GetType().GetCustomAttributes(typeof(DOCratosActionAttribute), false);

					// Make sure we have data to work with
					if (allActions != null)
					{
						foreach (DOCratosActionAttribute currentAction in allActions)
						{
							if (currentAction.CratosCollectionAction != DOCratosCollectionAction.None) { this.doActions.Add(currentAction); }
						}
					}
				}

				// Return to calling method
				return (this.doActions);
			}
		}

		/// <summary>
		/// Data Object List Action
		/// </summary>
		public DOCratosCollectionAction CollectionAction
		{
			get { return (this.collectionAction); }
			set
			{
				// Save the new value
				this.collectionAction = value;

				// Update the DB Connection Name if required
				foreach (DOCratosActionAttribute currentAction in this.AllDOActions)
				{
					// Make sure the Connection Name is specified
					if (!string.IsNullOrEmpty(currentAction.DBConnectionName))
					{
						switch (this.collectionAction)
						{
							case DOCratosCollectionAction.Retrieve:
								if ((currentAction.CratosCollectionAction == this.collectionAction) &&
										(currentAction.CratosActionType == this.DataObject.DBActionType))
								{
									this.DataObject.DBConnectionName = currentAction.DBConnectionName;
								}
								break;
						}
					}
				}
			}
		}

		/// <summary>
		/// Error Message
		/// </summary>
		[XmlIgnore]
		public string ErrorMessage
		{
			get
			{
				// Declare the required variables
				StringBuilder returnValue = new StringBuilder();

				// Make sure we have errors
				if (this.Errors.Count > 0)
				{
					// Process all the errors
					foreach (CratosError currentError in this.Errors)
					{
						if (currentError.RuntimeException == null)
						{
							returnValue.AppendLine(string.Format("[{0}-{1}] [{2}] {3}", currentError.Date.ToString("dd/MM/yyyy HH:mm:ss"),
																																					currentError.Source,
																																					currentError.Severity.ToString(),
																																					currentError.Message));
						}
						else
						{
							returnValue.AppendLine(string.Format("[{0}-{1}] [{2}] {3}", currentError.Date.ToString("dd/MM/yyyy HH:mm:ss"),
																																					currentError.Source,
																																					currentError.Severity.ToString(),
																																					currentError.Message + "\n" + currentError.RuntimeException.ToString()));
						}
					}
				}
				else { returnValue.AppendLine("No errors found"); }

				// Return to calling method
				return (returnValue.ToString());
			}
		}

		/// <summary>
		/// Maximum No of Records to load
		/// </summary>
		public long MaxRecords { get; set; }

		/// <summary>
		/// Raptor Data Object
		/// </summary>
		public T DataObject { get; set; }
		#endregion

		#region Class Operators
		/// <summary>
		/// Cast from List of IDOCratos to DOCratosCollection of T
		/// </summary>
		/// <param name="doList">Incoming DO List</param>
		/// <returns>The new Collection</returns>
		public static explicit operator DOCratosCollection<T>(List<IDOCratos> doList)
		{
			// Declare the required variables
			DOCratosCollection<T> returnCollection;

			// Create the return collection
			returnCollection = new DOCratosCollection<T>();

			// Process all the items in the incoming list
			foreach (T item in doList)
			{
				if (item is T) { returnCollection.Add((T)item); }
				else
				{
					throw new InvalidCastException(string.Format("Cannot cast {0} as {1}.", item.GetType(), typeof(T)));
				}
			}

			// Return to calling method
			return (returnCollection);
		}

		/// <summary>
		/// Cast from List of IDOCratos to DOCratosCollection of T
		/// </summary>
		/// <param name="doList">Incoming DO List</param>
		/// <returns>The new Collection</returns>
		public static explicit operator List<T>(DOCratosCollection<T> doList)
		{
			// Declare the required variables
			List<T> returnCollection;

			// Create the return collection
			returnCollection = new List<T>();

			// Process all the items in the incoming list
			foreach (T item in doList)
			{
				if (item is T) { returnCollection.Add((T)item); }
				else
				{
					throw new InvalidCastException(string.Format("Cannot cast {0} as {1}.", item.GetType(), typeof(T)));
				}
			}

			// Return to calling method
			return (returnCollection);
		}
		#endregion

		#region Class Methods
		/// <summary>
		/// Create a new copy of the object
		/// </summary>
		/// <returns>A new cloned object</returns>
		public virtual object Clone()
		{
			// Return to calling method
			return (DOCratosGlobal.CloneObject(this));
		}

		/// <summary>
		/// Perform the specified Action
		/// </summary>
		/// <returns>A boolean indicating the success of the method</returns>
		public virtual bool PerformAction()
		{
			// Declare the required variables
			bool															returnValue = false;
			ICratosEntityCollectionAccess<T>	entityAccess = null;

			try
			{
				// Clear any pending errors
				this.Errors.Clear();

				// Retrieve the Entity Access object
				entityAccess = this.GetEntityAccess();

				// Make sure we have an Entity object to work with
				if (entityAccess != null)
				{
					// Execute the relevant Action
					if (entityAccess.PerformAction(this)) { returnValue = true; }
					else
					{
						foreach (CratosError currentError in entityAccess.Errors) { this.Errors.Add(currentError); }
					}
				}
				else { throw new Exception("Failed to create the required Cratos Collection Access Type"); }
			}
			catch (Exception runtimeException)
			{
				// Create and setup a new error
				this.Errors.Add(new CratosError("DOCratosCollection.PerformAction", CratosLogSeverity.Exception,
																				"Runtime error ocurred", runtimeException));
			}

			// Return to calling method
			return (returnValue);
		}

		/// <summary>
		/// Validate the object against the current action
		/// </summary>
		/// <returns></returns>
		public virtual bool Validate()
		{
			// Declare the required variables

			// Clear any pending errors
			this.Errors.Clear();

			// Handle the Collection Action
			switch (this.CollectionAction)
			{
				case DOCratosCollectionAction.Retrieve:
				case DOCratosCollectionAction.Custom:
					// Validate the Data Object
					if (!this.DataObject.Validate())
					{
						// Add the relevant errors to the current collection
						foreach (CratosError currentError in this.DataObject.Errors) { this.Errors.Add(currentError); }
					}

					break;

				case DOCratosCollectionAction.Various:
					// Process all the items in the collection
					foreach (IDOCratos currentItem in this)
					{
						if (!currentItem.Validate())
						{
							// Add the relevant errors to the current collection
							foreach (CratosError currentError in currentItem.Errors) { this.Errors.Add(currentError); }
						}
					}
					break;
			}

			// Return to calling method
			return (this.Errors.Count <= 0);
		}

		#region Rule Methods
		/// <summary>
		/// Initialize the DO Rules
		/// </summary>
		public virtual void InitializeRules()
		{
			// Declare the required variables

			// Creat a new Rule object if required
			if (this.currentRules == null) { this.currentRules = new Dictionary<DOCratosCollectionAction, List<CratosRule>>(); }

			// Clear all current Rules
			this.currentRules.Clear();

			// Return to calling method
			return;
		}

		/// <summary>
		/// Add Custom Rule to Data Object Validation
		/// </summary>
		/// <param name="collectionAction">Cratos Collection Action</param>
		/// <param name="newRule">New Cratos Rule</param>
		public void AddRule(DOCratosCollectionAction collectionAction, CratosRule newRule)
		{
			// Declare the required variables
			List<CratosRule> newRuleList;

			// Add the Rule appropriately
			if (this.currentRules.ContainsKey(collectionAction))
			{
				// Add the Rule to the Rule List
				this.currentRules[collectionAction].Add(newRule);
			}
			else
			{
				// Create the Rule List and add the Cratos Rule
				newRuleList = new List<CratosRule>();
				newRuleList.Add(newRule);

				// Add the item to the Rule Dictionary
				this.currentRules.Add(collectionAction, newRuleList);
			}

			// Return to calling method
			return;
		}

		/// <summary>
		/// Validate all the relevant Rules
		/// </summary>
		/// <param name="collectionAction">Cratos Collection Action</param>
		/// <returns>A boolean indicating the success of the method</returns>
		public bool ValidateRules(DOCratosCollectionAction collectionAction)
		{
			// Declare the required variables
			bool returnValue = true;
			List<CratosRule> actionRules;

			try
			{
				// Make sure we have Rules for the associated Action
				if ((this.currentRules != null) && (this.currentRules.ContainsKey(collectionAction)))
				{
					// Retrieve the Action rules
					actionRules = this.currentRules[collectionAction];

					// Process all the Rules
					foreach (CratosRule currentRule in actionRules)
					{
						if (!currentRule.IsValid)
						{
							this.Errors.Add(new CratosError("Validation Rules", CratosLogSeverity.ValidationError, currentRule.Description));
							returnValue = false;
						}
					}
				}
			}
			catch (Exception runtimeException)
			{
				// Create and setup a new error
				this.Errors.Add(new CratosError("DOCratosCollection.ValidateRules", CratosLogSeverity.Exception,
																				"Runtime error ocurred", runtimeException));
			}

			// Return to calling method
			return (returnValue);
		}
		#endregion

		/// <summary>
		/// Display the current Errors in the error collection
		/// </summary>
		/// <param name="appTitle">Application Title</param>
		public virtual void DisplayError(string appTitle)
		{
			// Declare the required variables
			StringBuilder errorMessage;

			// Build the error message
			errorMessage = new StringBuilder();
			foreach (CratosError currentError in this.Errors)
			{
				if (currentError.RuntimeException == null)
				{
					errorMessage.AppendLine(string.Format("[{0} - {1}] [{2}] {3}", currentError.Date.ToString("dd/MM/yyyy HH:mm:ss"),
																																				 currentError.Source,
																																				 currentError.Severity.ToString(),
																																				 currentError.Message));
				}
				else
				{
					errorMessage.AppendLine(string.Format("[{0} - {1}] [{2}] {3}\n{4}", currentError.Date.ToString("dd/MM/yyyy HH:mm:ss"),
																																							currentError.Source,
																																							currentError.Severity.ToString(),
																																							currentError.Message,
																																							currentError.RuntimeException.ToString()));
				}
			}

			// Display the error to the user
			MessageBox.Show(errorMessage.ToString(), appTitle, MessageBoxButton.OK, MessageBoxImage.Error, MessageBoxResult.OK);

			// Return to calling method
			return;
		}

		/// <summary>
		/// Display the current Errors in the error collection
		/// </summary>
		/// <param name="appTitle">Application Title</param>
		public void DisplayValidation(string appTitle)
		{
			// Declare the required variables
			StringBuilder errorMessage;

			// Build the error message
			errorMessage = new StringBuilder();
			foreach (CratosError currentError in this.Errors)
			{
				if (currentError.RuntimeException == null)
				{
					errorMessage.AppendLine(currentError.Message);
				}
				else
				{
					errorMessage.AppendLine(string.Format("[{0} - {1}] [{2}] {3}\n{4}", currentError.Date.ToString("dd/MM/yyyy HH:mm:ss"),
																																							currentError.Source,
																																							currentError.Severity.ToString(),
																																							currentError.Message,
																																							currentError.RuntimeException.ToString()));
				}
				errorMessage.AppendLine();
			}

			// Display the error to the user
			MessageBox.Show(errorMessage.ToString(), appTitle, MessageBoxButton.OK, MessageBoxImage.Information, MessageBoxResult.OK);

			// Return to calling method
			return;
		}

		/// <summary>
		/// Retrieve the Collection
		/// </summary>
		/// <returns></returns>
		public Collection<IDOCratos> GetCollection()
		{
			// Declare the required variables
			Collection<IDOCratos> returnValue;

			// Create the new List
			returnValue = new Collection<IDOCratos>();

			// Process all the relevant items
			foreach (IDOCratos currentItem in this) { returnValue.Add(currentItem); }

			// Return to calling method
			return (returnValue);
		}

		/// <summary>
		/// Retrieve the List
		/// </summary>
		/// <returns></returns>
		public List<IDOCratos> GetList()
		{
			// Declare the required variables
			List<IDOCratos> returnValue;

			// Create the new List
			returnValue = new List<IDOCratos>();

			// Process all the relevant items
			foreach (IDOCratos currentItem in this) { returnValue.Add(currentItem); }

			// Return to calling method
			return (returnValue);
		}

		/// <summary>
		/// String Representation of the object
		/// </summary>
		/// <returns>String Representation</returns>
		public override string ToString()
		{
			// Declare the required variables
			StringBuilder returnValue;

			// Make sure we have data to work with
			if (this.Count <= 0) { return ("No Items"); }
			else
			{
				// Create the String Builder
				returnValue = new StringBuilder();

				// Process all the items in the list
				foreach (T currentItem in this.OrderBy<T, string>(item => item.ToString()))
				{
					returnValue.AppendFormat("{0},", currentItem.ToString());
				}
			}

			// Return to calling method
			return (returnValue.ToString().Substring(0, (returnValue.ToString().Length - 1)));
		}

		/// <summary>
		/// Notify Property Changed method
		/// </summary>
		/// <param name="changeInfo">Change information</param>
		protected virtual void NotifyPropertyChanged(string changeInfo)
		{
			// Declare the required variables

			// Make sure we have somebody listening
			if (this.PropertyChanged != null)
			{
				// Raise the Property Changed event
				this.PropertyChanged(this, new PropertyChangedEventArgs(changeInfo));
			}

			// Return to calling method
			return;
		}

		/// <summary>
		/// Create Serialize Stream from object
		/// </summary>
		/// <returns>
		/// A byte array of the serialized object
		/// </returns>
		public virtual byte[] CreateSerializeStream()
		{
			// Return to calling method
			return (DOCratosGlobal.BinSerialize(this));
		}

		/// <summary>
		/// Create Object from a Serialized object
		/// </summary>
		/// <param name="objectStream">A byte array representing the serialized object</param>
		/// <returns>
		/// The recreated object
		/// </returns>
		public virtual object CreateObjectFromStream(byte[] objectStream)
		{
			// Throw an exception
			throw new NotImplementedException();
		}
		#endregion

		#region Class Support Methods
		private ICratosEntityCollectionAccess<T> GetEntityAccess()
		{
			// Declare the required variables
			Type															doType, doGenericType;
			ICratosEntityCollectionAccess<T>	entityAccess = null;

			try
			{
				// Create the relevant type
				if (this.IgnoreUnity)
				{
					// Create the required Generic Type
					doType                   = Type.GetType("Cratos.Server.Entity.CratosEntityCollection`1, Cratos.Server.Entity");
					doGenericType            = doType.MakeGenericType(new Type[] { this.DataObject.GetType() });
					entityAccess             = ((ICratosEntityCollectionAccess<T>)Activator.CreateInstance(doGenericType));
					entityAccess.IgnoreUnity = true;
				}
				else
				{
					entityAccess = DOCratosGlobal.UnityContainer.Resolve<ICratosEntityCollectionAccess<T>>(DOCratosGlobal.EntityCollectionAccessMethod);
				}
			}
			catch (Exception runtimeException)
			{
				// Create and setup a new error
				this.Errors.Add(new CratosError("DOCratosCollection.PerformAction", CratosLogSeverity.Exception,
																				"Runtime error ocurred", runtimeException));
			}

			// Return to calling method
			return (entityAccess);
		}
		#endregion
	}
}
